/*
 *		This Code Was Created By Jeff Molofee 2000
 *		A HUGE Thanks To Fredric Echols For Cleaning Up
 *		And Optimizing The Base Code, Making It More Flexible!
 *		If You've Found This Code Useful, Please Let Me Know.
 *		Visit My Site At nehe.gamedev.net
 */

#include <windows.h>		// Header File For Windows
#include <stdio.h>			// Header File For Standard Input/Output
#include "gl/glew.h"			// Header File For The OpenGL32 Library
#include "gl/wglew.h"
#include <GL/GL.h>
#include <thread>
#include <mutex>
#include <vector>
#include <condition_variable>
#include <chrono>
#include <ctime>
#include "TMath.h"

HINSTANCE	hInstance;		// Holds The Instance Of The Application

GLuint	xloop;				// Loop For X Axis
GLuint	yloop;				// Loop For Y Axis

static int windsize = 256;

enum EBuffer
{
	VERTEX,
	ELEMENT,
	TRANSFORM,
	MATERIAL,
	MAX
};

enum EAttrib
{
	POSITION = 0,
	NORMAL = 1,
	TEXCOORD = 2,
	COLOR = 3,
	DRAW_ID = 4
};

class GLWindow
{
public:
	GLWindow() : hDC(0), hRC1(0), hRC2(0), hWnd(0), active(TRUE) {}
	HDC			hDC;		// Private GDI Device Context
	HGLRC		hRC1;		// Permanent Rendering Context
	HGLRC		hRC2;		// Permanent Rendering Context
	HWND		hWnd;		// Holds Our Window Handle
	bool	keys[256];		// Array Used For The Keyboard Routine
	bool	active;			// Window Active Flag Set To TRUE By Default
	GLfloat	xrot;			// Rotates Cube On The X Axis
	GLfloat	yrot;			// Rotates Cube On The Y Axis
	int		width;
	int		height;

	int		bRender1;
	int		bRender2;

	GLuint		TextureName;
	GLuint		ProgramName;
	GLuint		UniformTransform;
	GLuint		UniformDiffuse;
	GLuint		BufferName[EBuffer::MAX];
	GLuint		VertexArrayName;
	GLsync		SyncName;

	GLuint		Framebuffer;
	GLuint		Renderbuffer;
	GLuint		FramebufferTexture;
	GLuint		RenderbufferTexture;

	std::thread	thread1;
	std::mutex	mutex1;
	std::condition_variable condition1;

	std::thread	thread2;
	std::mutex	mutex2;
	std::condition_variable condition2;
};

GLWindow	_GLWindow[4];

LRESULT	CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);	// Declaration For WndProc

float VertexArray[] = {
	// Bottom Face
	-1.0f, -1.0f, -1.0f, 1.0f, 1.0f,
	1.0f, -1.0f, -1.0f, 0.0f, 1.0f,
	1.0f, -1.0f, 1.0f, 0.0f, 0.0f,
	-1.0f, -1.0f, 1.0f, 1.0f, 0.0f,
	// Front Face
	-1.0f, -1.0f, 1.0f, 0.0f, 0.0f,
	1.0f, -1.0f, 1.0f, 1.0f, 0.0f,
	1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
	-1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
	// Back Face
	-1.0f, -1.0f, -1.0f, 1.0f, 0.0f,
	-1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
	1.0f, 1.0f, -1.0f, 0.0f, 1.0f,
	1.0f, -1.0f, -1.0f, 0.0f, 0.0f,
	// Right face
	1.0f, -1.0f, -1.0f, 1.0f, 0.0f,
	1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
	1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
	1.0f, -1.0f, 1.0f, 0.0f, 0.0f,
	// Left Face
	-1.0f, -1.0f, -1.0f, 0.0f, 0.0f,
	-1.0f, -1.0f, 1.0f, 1.0f, 0.0f,
	-1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
	-1.0f, 1.0f, -1.0f, 0.0f, 1.0f,
	// Top Face
	-1.0f, 1.0f, -1.0f, 0.0f, 1.0f,
	-1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
	1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
	1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
};

unsigned short VertexElementArray[] = {
	0, 1, 2, 0, 2, 3,
	4, 5, 6, 4, 6, 7,
	8, 9, 10, 8, 10, 11,
	12, 13, 14, 12, 14, 15,
	16, 17, 18, 16, 18, 19,
	20, 21, 22, 20, 22, 23,
};

void checkGLError(void)
{
	GLenum nError = glGetError();
	if (nError != GL_NO_ERROR)
	{
		printf("An OpenGL Error Ocurr!\n");
	}
}

bool checkProgram(GLuint ProgramName)
{
	if (!ProgramName)
		return false;

	GLint Result = GL_FALSE;
	glGetProgramiv(ProgramName, GL_LINK_STATUS, &Result);

	if (Result == GL_TRUE)
		return true;

	//fprintf(stdout, "Linking program\n");
	int InfoLogLength;
	glGetProgramiv(ProgramName, GL_INFO_LOG_LENGTH, &InfoLogLength);
	if (InfoLogLength > 0)
	{
		std::string Buffer; Buffer.resize(__max(InfoLogLength, int(1)));
		glGetProgramInfoLog(ProgramName, InfoLogLength, NULL, &Buffer[0]);
		fprintf(stdout, "%s\n", &Buffer[0]);
	}

	return Result == GL_TRUE;
}

int InitGL(int id)										// All Setup For OpenGL Goes Here
{
	//	init texture
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glGenTextures(1, &_GLWindow[id].TextureName);

	unsigned char pPixels[256 * 256 * 4];
	unsigned char* ppPixels = pPixels;

	int R = 0xFF * ((float)rand() / (float)RAND_MAX);
	int G = 0xFF * ((float)rand() / (float)RAND_MAX);
	int B = 0xFF * ((float)rand() / (float)RAND_MAX);

	for (int i = 0; i < 256; i++)
	for (int j = 0; j < 256; j++)
	{
		if ((i / 16 % 2) == 0 && (j / 16 % 2) == 0)
		{
			*ppPixels++ = R;
			*ppPixels++ = G;
			*ppPixels++ = B;
		}
		else
		{
			*ppPixels++ = B;
			*ppPixels++ = G;
			*ppPixels++ = R;
		}
		*ppPixels++ = 0xFF;
	}
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, _GLWindow[id].TextureName);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 256, 256, 0, GL_BGRA, GL_UNSIGNED_BYTE, pPixels);
	glGenerateMipmap(GL_TEXTURE_2D);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	checkGLError();
	//	init program
	char const *vert = "\
	#version 150 core\n\
	uniform transform\n\
	{\n\
		mat4 MVP;\n\
	} Transform;\n\
	in vec3 Position;\n\
	in vec2 Texcoord;\n\
	out block\n\
	{\n\
		vec2 Texcoord;\n\
	} Out;\n\
	void main()\n\
	{\n\
		Out.Texcoord = Texcoord;\n\
		gl_Position = Transform.MVP * vec4(Position, 1.0);\n\
	}\n";

	char const* frag = "\
	#version 150 core\n\
	uniform sampler2D Diffuse;\n\
	in block\n\
	{\n\
		vec2 Texcoord;\n\
	} In;\n\
	out vec4 Color;\n\
	void main()\n\
	{\n\
		Color = texture(Diffuse, In.Texcoord);\n\
	}\n";

	GLuint VertShaderName = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(VertShaderName, 1, (const char**)&vert, NULL);
	glCompileShader(VertShaderName);
	checkGLError();
	GLuint FragShaderName = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(FragShaderName, 1, (const char**)&frag, NULL);
	glCompileShader(FragShaderName);
	checkGLError();
	_GLWindow[id].ProgramName = glCreateProgram();
	glAttachShader(_GLWindow[id].ProgramName, VertShaderName);
	glAttachShader(_GLWindow[id].ProgramName, FragShaderName);
	checkGLError();
	glBindAttribLocation(_GLWindow[id].ProgramName, EAttrib::POSITION, "Position");
	glBindAttribLocation(_GLWindow[id].ProgramName, EAttrib::TEXCOORD, "Texcoord");
	glBindFragDataLocation(_GLWindow[id].ProgramName, 0, "Color");
	glLinkProgram(_GLWindow[id].ProgramName);
	checkGLError();
	glDeleteShader(VertShaderName);
	glDeleteShader(FragShaderName);

	_GLWindow[id].UniformTransform = glGetUniformBlockIndex(_GLWindow[id].ProgramName, "transform");
	_GLWindow[id].UniformDiffuse = glGetUniformLocation(_GLWindow[id].ProgramName, "Diffuse");
	checkGLError();

	checkProgram(_GLWindow[id].ProgramName);

	//	init buffer
	glGenBuffers(EBuffer::MAX, _GLWindow[id].BufferName);
	glBindBuffer(GL_ARRAY_BUFFER, _GLWindow[id].BufferName[EBuffer::VERTEX]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(VertexArray), VertexArray, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _GLWindow[id].BufferName[EBuffer::ELEMENT]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(VertexElementArray), VertexElementArray, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	checkGLError();

	GLint UniformBlockSize = 0;
	glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &UniformBlockSize);
	glBindBuffer(GL_UNIFORM_BUFFER, _GLWindow[id].BufferName[EBuffer::TRANSFORM]);
	glBufferData(GL_UNIFORM_BUFFER, UniformBlockSize, NULL, GL_DYNAMIC_DRAW);
	glBindBuffer(GL_UNIFORM_BUFFER, 0);
	checkGLError();


	//	init Framebuffer Texture
	glGenTextures(1, &_GLWindow[id].FramebufferTexture);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, _GLWindow[id].FramebufferTexture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, GLint(0), GL_RGBA8, _GLWindow[id].width, _GLWindow[id].height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

	glGenTextures(1, &_GLWindow[id].RenderbufferTexture);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, _GLWindow[id].RenderbufferTexture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexImage2D(GL_TEXTURE_2D, GLint(0), GL_DEPTH_COMPONENT24, _GLWindow[id].width, _GLWindow[id].height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

	////	init Framebuffer
	//glGenFramebuffers(1, &_GLWindow[id].Framebuffer);
	//glBindFramebuffer(GL_FRAMEBUFFER, _GLWindow[id].Framebuffer);
	//glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, _GLWindow[id].FramebufferTexture, 0);
	//glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, _GLWindow[id].RenderbufferTexture, 0);

	//GLenum nFBError = glCheckFramebufferStatus(GL_FRAMEBUFFER);

	//glBindFramebuffer(GL_FRAMEBUFFER, 0);

	checkGLError();


	////	init VertexArray
	//glGenVertexArrays(1, &_GLWindow[id].VertexArrayName);
	//glBindVertexArray(_GLWindow[id].VertexArrayName);
	//glBindBuffer(GL_ARRAY_BUFFER, _GLWindow[id].BufferName[EBuffer::VERTEX]);
	//glVertexAttribPointer(EAttrib::POSITION, 3, GL_FLOAT, GL_FALSE, 20, (char*)NULL);
	//glVertexAttribPointer(EAttrib::TEXCOORD, 2, GL_FLOAT, GL_FALSE, 20, (char*)NULL + 12);
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _GLWindow[id].BufferName[EBuffer::ELEMENT]);
	//glBindBuffer(GL_ARRAY_BUFFER, 0);
	//checkGLError();
	//glEnableVertexAttribArray(EAttrib::POSITION);
	//glEnableVertexAttribArray(EAttrib::TEXCOORD);
	//glBindVertexArray(0);
	//checkGLError();
	
	return TRUE;										// Initialization Went OK
}

int DrawGLScene(int id)									// Here's Where We Do All The Drawing
{
	checkGLError();
	{
		glBindFramebuffer(GL_FRAMEBUFFER, _GLWindow[id].Framebuffer);

		glViewport(0, 0, _GLWindow[id].width, _GLWindow[id].height);
		float clearColor[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
		glClearBufferfv(GL_COLOR, 0, clearColor);
		checkGLError();

		glDisable(GL_CULL_FACE);

		_GLWindow[id].SyncName = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
		checkGLError();

		glUseProgram(_GLWindow[id].ProgramName);
		glUniform1i(_GLWindow[id].UniformDiffuse, 0);
		glUniformBlockBinding(_GLWindow[id].ProgramName, _GLWindow[id].UniformTransform, 1);
		checkGLError();

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, _GLWindow[id].TextureName);
		checkGLError();
		glBindBufferBase(GL_UNIFORM_BUFFER, 1, _GLWindow[id].BufferName[EBuffer::TRANSFORM]);
		GLboolean bValid = glIsVertexArray(_GLWindow[id].VertexArrayName);
		glBindVertexArray(_GLWindow[id].VertexArrayName);
		checkGLError();

		for (yloop = 1; yloop < 6; yloop++)
		{
			for (xloop = 0; xloop < yloop; xloop++)
			{
				glBindBuffer(GL_UNIFORM_BUFFER, _GLWindow[id].BufferName[EBuffer::TRANSFORM]);
				checkGLError();

				Matrix4f* modelviewperspective = (Matrix4f*)glMapBufferRange(
					GL_UNIFORM_BUFFER, 0, sizeof(float)* 16,
					GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
				checkGLError();

				Matrix4f	perspective1;
				perspective1.perspective(45.0f * _Radius, (GLfloat)_GLWindow[id].width / (GLfloat)_GLWindow[id].height, 0.1f, 100.0f);

				Matrix4f	modelview1, rotate1, rotate2;
				modelview1.translate(Vector3f(1.4f + (float(xloop)*2.8f) - (float(yloop)*1.4f), ((6.0f - float(yloop))*2.4f) - 7.0f, -10.0f));
				rotate1.rotate((45.0f - (2.0f*yloop) + _GLWindow[id].xrot) * _Radius, Vector3f(1.0f, 0.0f, 0.0f));
				rotate2.rotate((45.0f + _GLWindow[id].yrot) * _Radius, Vector3f(0.0f, 1.0f, 0.0f));

				Matrix4f finalMatrix = perspective1 * (modelview1 * rotate1 * rotate2);

				memcpy(modelviewperspective, finalMatrix, sizeof(float)* 16);
				float* fm = finalMatrix;

				// Make sure the uniform buffer is uploaded
				glUnmapBuffer(GL_UNIFORM_BUFFER);
				checkGLError();

				glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, NULL, 1);

				checkGLError();
			}
		}
		checkGLError();

		GLint64 MaxTimeout(1000);
		GLenum Result = glClientWaitSync(_GLWindow[id].SyncName, GL_SYNC_FLUSH_COMMANDS_BIT, (GLuint64)MaxTimeout);
		checkGLError();

		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}


	{
		glViewport(0, 0, _GLWindow[id].width, _GLWindow[id].height);
		float clearColor[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
		glClearBufferfv(GL_COLOR, 0, clearColor);
		checkGLError();

		glDisable(GL_CULL_FACE);

		_GLWindow[id].SyncName = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
		checkGLError();

		glUseProgram(_GLWindow[id].ProgramName);
		glUniform1i(_GLWindow[id].UniformDiffuse, 0);
		glUniformBlockBinding(_GLWindow[id].ProgramName, _GLWindow[id].UniformTransform, 1);
		checkGLError();

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, _GLWindow[id].FramebufferTexture);
		checkGLError();
		glBindBufferBase(GL_UNIFORM_BUFFER, 1, _GLWindow[id].BufferName[EBuffer::TRANSFORM]);
		GLboolean bValid = glIsVertexArray(_GLWindow[id].VertexArrayName);
		glBindVertexArray(_GLWindow[id].VertexArrayName);
		checkGLError();

		for (yloop = 1; yloop < 6; yloop++)
		{
			for (xloop = 0; xloop < yloop; xloop++)
			{
				glBindBuffer(GL_UNIFORM_BUFFER, _GLWindow[id].BufferName[EBuffer::TRANSFORM]);
				checkGLError();

				Matrix4f* modelviewperspective = (Matrix4f*)glMapBufferRange(
					GL_UNIFORM_BUFFER, 0, sizeof(float)* 16,
					GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
				checkGLError();

				Matrix4f	perspective1;
				perspective1.perspective(45.0f * _Radius, (GLfloat)_GLWindow[id].width / (GLfloat)_GLWindow[id].height, 0.1f, 100.0f);

				Matrix4f	modelview1, rotate1, rotate2;
				modelview1.translate(Vector3f(1.4f + (float(xloop)*2.8f) - (float(yloop)*1.4f), ((6.0f - float(yloop))*2.4f) - 7.0f, -10.0f));
				rotate1.rotate((45.0f - (2.0f*yloop) + _GLWindow[id].xrot) * _Radius, Vector3f(1.0f, 0.0f, 0.0f));
				rotate2.rotate((45.0f + _GLWindow[id].yrot) * _Radius, Vector3f(0.0f, 1.0f, 0.0f));

				Matrix4f finalMatrix = perspective1 * (modelview1 * rotate1 * rotate2);

				memcpy(modelviewperspective, finalMatrix, sizeof(float)* 16);

				// Make sure the uniform buffer is uploaded
				glUnmapBuffer(GL_UNIFORM_BUFFER);
				checkGLError();

				glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, NULL, 1);

				checkGLError();
			}
		}
		checkGLError();

		GLint64 MaxTimeout(1000);
		GLenum Result = glClientWaitSync(_GLWindow[id].SyncName, GL_SYNC_FLUSH_COMMANDS_BIT, (GLuint64)MaxTimeout);
		checkGLError();
	}

	return TRUE;										// Keep Going
}

GLvoid KillGLWindow(int id)								// Properly Kill The Window
{
	try
	{
		_GLWindow[id].mutex2.lock();
		_GLWindow[id].bRender2 = 0;
		_GLWindow[id].mutex2.unlock();

		std::unique_lock<std::mutex> lk2(_GLWindow[id].mutex2);
		_GLWindow[id].condition2.wait(lk2);

		_GLWindow[id].mutex1.lock();
		_GLWindow[id].bRender1 = 0;
		_GLWindow[id].mutex1.unlock();

		std::unique_lock<std::mutex> lk1(_GLWindow[id].mutex1);
		_GLWindow[id].condition1.wait(lk1);

		_GLWindow[id].thread1.join();
		_GLWindow[id].thread2.join();
	}
	catch (...)
	{
		int c = 0;
	}

	if (_GLWindow[id].hRC2)											// Do We Have A Rendering Context?
	{
		//HGLRC hRC = wglGetCurrentContext();
		//DWORD dLastError = GetLastError();
		//if (!wglMakeCurrent(NULL,NULL))					// Are We Able To Release The DC And RC Contexts?
		//{
		//	dLastError = GetLastError();
		//	MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		//}

		if (!wglDeleteContext(_GLWindow[id].hRC2))						// Are We Able To Delete The RC?
		{
			MessageBox(NULL, "Release Rendering Context Failed.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
		}
		_GLWindow[id].hRC2 = NULL;

		if (!wglDeleteContext(_GLWindow[id].hRC1))						// Are We Able To Delete The RC?
		{
			MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		}
		_GLWindow[id].hRC1 = NULL;										// Set RC To NULL
	}

	if (_GLWindow[id].hDC && !ReleaseDC(_GLWindow[id].hWnd, _GLWindow[id].hDC))					// Are We Able To Release The DC
	{
		MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		_GLWindow[id].hDC = NULL;										// Set DC To NULL
	}

	if (_GLWindow[id].hWnd && !DestroyWindow(_GLWindow[id].hWnd))					// Are We Able To Destroy The Window?
	{
		MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		_GLWindow[id].hWnd = NULL;										// Set hWnd To NULL
	}
}

void DrawGLSceneThread1(int id)
{
	{
		std::unique_lock<std::mutex> lk1(_GLWindow[id].mutex1);
		_GLWindow[id].condition1.wait(lk1);
	}

	wglMakeCurrent(_GLWindow[id].hDC, _GLWindow[id].hRC1);					// Try To Activate The Rendering Context
	checkGLError();
	InitGL(id);									// Initialize Our Newly Created GL Window

	GLchar* pVersion = 0;
	int glVersion[2] = { -1, -1 }; // Set some default values for the version
	pVersion = (GLchar*)glGetString(GL_VERSION);
	glGetIntegerv(GL_MAJOR_VERSION, &glVersion[0]); // Get back the OpenGL MAJOR version we are using
	glGetIntegerv(GL_MINOR_VERSION, &glVersion[1]); // Get back the OpenGL MAJOR version we are using

	_GLWindow[id].condition2.notify_one();

	std::chrono::time_point<std::chrono::system_clock> start, end;
	std::chrono::duration<double> elapsed_seconds;
	start = std::chrono::system_clock::now();

	//	init texture
	while (true)
	{
		end = std::chrono::system_clock::now();
		elapsed_seconds = end - start;

		if (elapsed_seconds.count() >= 0.5)
		{
			start = end;

			unsigned char pPixels[256 * 256 * 4];
			unsigned char* ppPixels = pPixels;

			int R = 0xFF * ((float)rand() / (float)RAND_MAX);
			int G = 0xFF * ((float)rand() / (float)RAND_MAX);
			int B = 0xFF * ((float)rand() / (float)RAND_MAX);

			for (int i = 0; i < 256; i++)
			for (int j = 0; j < 256; j++)
			{
				if ((i / 16 % 2) == 0 && (j / 16 % 2) == 0)
				{
					*ppPixels++ = R;
					*ppPixels++ = G;
					*ppPixels++ = B;
				}
				else
				{
					*ppPixels++ = B;
					*ppPixels++ = G;
					*ppPixels++ = R;
				}
				*ppPixels++ = 0xFF;
			}

			std::lock_guard<std::mutex> lg(_GLWindow[id].mutex2);

			glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
			glActiveTexture(GL_TEXTURE0);
			glBindTexture(GL_TEXTURE_2D, _GLWindow[id].TextureName);
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 256, GL_BGRA, GL_UNSIGNED_BYTE, pPixels);
			glGenerateMipmap(GL_TEXTURE_2D);
			glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
		}

		std::this_thread::sleep_for(std::chrono::microseconds(1));

		if (_GLWindow[id].bRender1 == 0)
			break;
	}

	checkGLError();
	glDeleteSync(_GLWindow[id].SyncName);
	glDeleteBuffers(EBuffer::MAX, _GLWindow[id].BufferName);
	glDeleteProgram(_GLWindow[id].ProgramName);
	glDeleteTextures(1, &_GLWindow[id].TextureName);
	glDeleteVertexArrays(1, &_GLWindow[id].VertexArrayName);

	checkGLError();
	wglMakeCurrent(NULL, NULL);

	_GLWindow[id].condition1.notify_one();
}

void DrawGLSceneThread2(int id)
{
	{
		std::unique_lock<std::mutex> lk2(_GLWindow[id].mutex2);
		_GLWindow[id].condition2.wait(lk2);
	}
	wglMakeCurrent(_GLWindow[id].hDC, _GLWindow[id].hRC2);					// Try To Activate The Rendering Context
	checkGLError();

	GLchar* pVersion = 0;
	int glVersion[2] = { -1, -1 }; // Set some default values for the version
	pVersion = (GLchar*)glGetString(GL_VERSION);
	glGetIntegerv(GL_MAJOR_VERSION, &glVersion[0]); // Get back the OpenGL MAJOR version we are using
	glGetIntegerv(GL_MINOR_VERSION, &glVersion[1]); // Get back the OpenGL MAJOR version we are using


	//	VertexArrayObject MUST initialized in RenderThread !!!
	//	init VertexArray
	glGenVertexArrays(1, &_GLWindow[id].VertexArrayName);
	glBindVertexArray(_GLWindow[id].VertexArrayName);
	glBindBuffer(GL_ARRAY_BUFFER, _GLWindow[id].BufferName[EBuffer::VERTEX]);
	glVertexAttribPointer(EAttrib::POSITION, 3, GL_FLOAT, GL_FALSE, 20, (char*)NULL);
	glVertexAttribPointer(EAttrib::TEXCOORD, 2, GL_FLOAT, GL_FALSE, 20, (char*)NULL + 12);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _GLWindow[id].BufferName[EBuffer::ELEMENT]);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	checkGLError();
	glEnableVertexAttribArray(EAttrib::POSITION);
	glEnableVertexAttribArray(EAttrib::TEXCOORD);
	glBindVertexArray(0);
	checkGLError();

	//	init Framebuffer
	glGenFramebuffers(1, &_GLWindow[id].Framebuffer);
	glBindFramebuffer(GL_FRAMEBUFFER, _GLWindow[id].Framebuffer);
	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, _GLWindow[id].FramebufferTexture, 0);
	glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, _GLWindow[id].RenderbufferTexture, 0);

	GLenum nFBError = glCheckFramebufferStatus(GL_FRAMEBUFFER);

	glBindFramebuffer(GL_FRAMEBUFFER, 0);


	while (true)
	{
		checkGLError();
		DrawGLScene(id);

		SwapBuffers(_GLWindow[id].hDC);					// Swap Buffers (Double Buffering)
		std::this_thread::sleep_for(std::chrono::microseconds(1));

		if (_GLWindow[id].bRender2 == 0)
			break;
	}

	checkGLError();
	wglMakeCurrent(NULL, NULL);

	_GLWindow[id].condition2.notify_one();

}


/*	This Code Creates Our OpenGL Window.  Parameters Are:					*
 *	title			- Title To Appear At The Top Of The Window				*
 *	width			- Width Of The GL Window Or Fullscreen Mode				*
 *	height			- Height Of The GL Window Or Fullscreen Mode			*
 *	bits			- Number Of Bits To Use For Color (8/16/24/32)			*
 *	fullscreenflag	- Use Fullscreen Mode (TRUE) Or Windowed Mode (FALSE)	*/
 
BOOL CreateGLWindow(char* title, int xpos, int ypos, int width, int height, int bits, int id)
{
	GLuint		PixelFormat;			// Holds The Results After Searching For A Match
	WNDCLASS	wc;						// Windows Class Structure
	DWORD		dwExStyle;				// Window Extended Style
	DWORD		dwStyle;				// Window Style
	RECT		WindowRect;				// Grabs Rectangle Upper Left / Lower Right Values
	WindowRect.left	 = xpos;			// Set Left Value To 0
	WindowRect.right = xpos + width;		// Set Right Value To Requested Width
	WindowRect.top	 = ypos;				// Set Top Value To 0
	WindowRect.bottom= ypos + height;		// Set Bottom Value To Requested Height

	wc.style			= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;	// Redraw On Size, And Own DC For Window.
	wc.lpfnWndProc		= (WNDPROC) WndProc;					// WndProc Handles Messages
	wc.cbClsExtra		= 0;									// No Extra Window Data
	wc.cbWndExtra		= 0;									// No Extra Window Data
	wc.hInstance		= hInstance;							// Set The Instance
	wc.hIcon			= LoadIcon(NULL, IDI_WINLOGO);			// Load The Default Icon
	wc.hCursor			= LoadCursor(NULL, IDC_ARROW);			// Load The Arrow Pointer
	wc.hbrBackground	= NULL;									// No Background Required For GL
	wc.lpszMenuName		= NULL;									// We Don't Want A Menu
	wc.lpszClassName	= title;								// Set The Class Name

	if (!RegisterClass(&wc))									// Attempt To Register The Window Class
	{
		MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		return FALSE;											// Return FALSE
	}
	
	dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;			// Window Extended Style
	dwStyle=WS_OVERLAPPEDWINDOW;							// Windows Style

	AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);		// Adjust Window To True Requested Size

	// Create The Window
	_GLWindow[id].hWnd = CreateWindowEx(dwExStyle,					// Extended Style For The Window
		title,								// Class Name
		title,								// Window Title
		dwStyle |							// Defined Window Style
		WS_CLIPSIBLINGS |					// Required Window Style
		WS_CLIPCHILDREN,					// Required Window Style
		xpos, ypos,							// Window Position
		WindowRect.right - WindowRect.left,	// Calculate Window Width
		WindowRect.bottom - WindowRect.top,	// Calculate Window Height
		NULL,								// No Parent Window
		NULL,								// No Menu
		hInstance,							// Instance
		NULL);								// Dont Pass Anything To WM_CREATE

	static	PIXELFORMATDESCRIPTOR pfd=				// pfd Tells Windows How We Want Things To Be
	{
		sizeof(PIXELFORMATDESCRIPTOR),				// Size Of This Pixel Format Descriptor
		1,											// Version Number
		PFD_DRAW_TO_WINDOW |						// Format Must Support Window
		PFD_SUPPORT_OPENGL |						// Format Must Support OpenGL
		PFD_DOUBLEBUFFER,							// Must Support Double Buffering
		PFD_TYPE_RGBA,								// Request An RGBA Format
		bits,										// Select Our Color Depth
		0, 0, 0, 0, 0, 0,							// Color Bits Ignored
		0,											// No Alpha Buffer
		0,											// Shift Bit Ignored
		0,											// No Accumulation Buffer
		0, 0, 0, 0,									// Accumulation Bits Ignored
		32,											// 16Bit Z-Buffer (Depth Buffer)  
		0,											// No Stencil Buffer
		0,											// No Auxiliary Buffer
		PFD_MAIN_PLANE,								// Main Drawing Layer
		0,											// Reserved
		0, 0, 0										// Layer Masks Ignored
	};

	_GLWindow[id].width = width;
	_GLWindow[id].height = height;
	_GLWindow[id].bRender2 = 1;
	_GLWindow[id].bRender1 = 1;
	
	if (!(_GLWindow[id].hDC = GetDC(_GLWindow[id].hWnd)))							// Did We Get A Device Context?
	{
		MessageBox(NULL, "Can't Create A GL Device Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
		return FALSE;								// Return FALSE
	}

	if (!(PixelFormat = ChoosePixelFormat(_GLWindow[id].hDC, &pfd)))	// Did Windows Find A Matching Pixel Format?
	{
		MessageBox(NULL, "Can't Find A Suitable PixelFormat.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
		return FALSE;								// Return FALSE
	}

	if (!SetPixelFormat(_GLWindow[id].hDC, PixelFormat, &pfd))		// Are We Able To Set The Pixel Format?
	{
		MessageBox(NULL, "Can't Set The PixelFormat.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
		return FALSE;								// Return FALSE
	}
	
	HGLRC hOldRC = 0;
	if (!(hOldRC = wglCreateContext(_GLWindow[id].hDC)))				// Are We Able To Get A Rendering Context?
	{
		MessageBox(NULL, "Can't Create A GL Rendering Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
		return FALSE;								// Return FALSE
	}

	if (!wglMakeCurrent(_GLWindow[id].hDC, hOldRC))					// Try To Activate The Rendering Context
	{
		MessageBox(NULL, "Can't Activate The GL Rendering Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
		return FALSE;								// Return FALSE
	}

	glewInit();

	GLchar* pVersion = 0;
	int glVersion[2] = { -1, -1 }; // Set some default values for the version

	pVersion = (GLchar*)glGetString(GL_VERSION);
	glGetIntegerv(GL_MAJOR_VERSION, &glVersion[0]); // Get back the OpenGL MAJOR version we are using
	glGetIntegerv(GL_MINOR_VERSION, &glVersion[1]); // Get back the OpenGL MAJOR version we are using

	int attributes[] = {
		WGL_CONTEXT_MAJOR_VERSION_ARB, 3, // Set the MAJOR version of OpenGL to 3
		WGL_CONTEXT_MINOR_VERSION_ARB, 3, // Set the MINOR version of OpenGL to 3
		WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, // Set our OpenGL context to be forward compatible
		0
	};

	_GLWindow[id].hRC1 = wglCreateContextAttribsARB(_GLWindow[id].hDC, NULL, attributes);	// Create and OpenGL 3.x context based on the given attributes
	_GLWindow[id].hRC2 = wglCreateContextAttribsARB(_GLWindow[id].hDC, NULL, attributes);	// Create and OpenGL 3.x context based on the given attributes

	if (!wglMakeCurrent(NULL, NULL))					// Try To Activate The Rendering Context
	{
		MessageBox(NULL, "Can't Activate The GL Rendering Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
		return FALSE;								// Return FALSE
	}
	wglDeleteContext(hOldRC); // Delete the temporary OpenGL 2.1 context
	wglShareLists(_GLWindow[id].hRC1, _GLWindow[id].hRC2);

	if (!wglMakeCurrent(_GLWindow[id].hDC, _GLWindow[id].hRC1))					// Try To Activate The Rendering Context
	{
		MessageBox(NULL, "Can't Activate The GL Rendering Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
		return FALSE;								// Return FALSE
	}

	pVersion = (GLchar*)glGetString(GL_VERSION);
	glGetIntegerv(GL_MAJOR_VERSION, &glVersion[0]); // Get back the OpenGL MAJOR version we are using
	glGetIntegerv(GL_MINOR_VERSION, &glVersion[1]); // Get back the OpenGL MAJOR version we are using

	if (!wglMakeCurrent(NULL, NULL))					// Try To Activate The Rendering Context
	{
		MessageBox(NULL, "Can't Activate The GL Rendering Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
		return FALSE;								// Return FALSE
	}

	ShowWindow(_GLWindow[id].hWnd, SW_SHOW);						// Show The Window
	SetForegroundWindow(_GLWindow[id].hWnd);						// Slightly Higher Priority
	SetFocus(_GLWindow[id].hWnd);									// Sets Keyboard Focus To The Window

	_GLWindow[id].thread1 = std::thread(DrawGLSceneThread1, id);
	_GLWindow[id].thread2 = std::thread(DrawGLSceneThread2, id);
	_GLWindow[id].condition1.notify_one();

	return TRUE;									// Success
}

LRESULT CALLBACK WndProc(	HWND	hWnd,			// Handle For This Window
							UINT	uMsg,			// Message For This Window
							WPARAM	wParam,			// Additional Message Information
							LPARAM	lParam)			// Additional Message Information
{
	int id = 0;
	for (id = 0; id < 4; id++)
	if (_GLWindow[id].hWnd == hWnd)
	break;

	switch (uMsg)									// Check For Windows Messages
	{
		case WM_ACTIVATE:							// Watch For Window Activate Message
		{
			if (!HIWORD(wParam))					// Check Minimization State
			{
				_GLWindow[id].active = TRUE;						// Program Is Active
			}
			else
			{
				_GLWindow[id].active = FALSE;						// Program Is No Longer Active
			}

			return 0;								// Return To The Message Loop
		}

		case WM_SYSCOMMAND:							// Intercept System Commands
		{
			switch (wParam)							// Check System Calls
			{
				case SC_SCREENSAVE:					// Screensaver Trying To Start?
				case SC_MONITORPOWER:				// Monitor Trying To Enter Powersave?
				return 0;							// Prevent From Happening
			}
			break;									// Exit
		}

		case WM_CLOSE:								// Did We Receive A Close Message?
		{
			PostQuitMessage(0);						// Send A Quit Message
			return 0;								// Jump Back
		}

		case WM_KEYDOWN:							// Is A Key Being Held Down?
		{
			std::lock_guard<std::mutex> lg(_GLWindow[id].mutex2);
			_GLWindow[id].keys[wParam] = TRUE;					// If So, Mark It As TRUE
			return 0;								// Jump Back
		}

		case WM_KEYUP:								// Has A Key Been Released?
		{
			std::lock_guard<std::mutex> lg(_GLWindow[id].mutex2);
			_GLWindow[id].keys[wParam] = FALSE;					// If So, Mark It As FALSE
			return 0;								// Jump Back
		}

		case WM_SIZE:
		{
			std::lock_guard<std::mutex> lg(_GLWindow[id].mutex2);
			_GLWindow[id].width = LOWORD(lParam);
			_GLWindow[id].height = HIWORD(lParam);
			return 0;
		}
	}

	// Pass All Unhandled Messages To DefWindowProc
	return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

int WINAPI WinMain(	HINSTANCE	hInstance1,			// Instance
					HINSTANCE	hPrevInstance,		// Previous Instance
					LPSTR		lpCmdLine,			// Command Line Parameters
					int			nCmdShow)			// Window Show State
{
	MSG		msg;									// Windows Message Structure
	BOOL	done=FALSE;								// Bool Variable To Exit Loop

	hInstance = GetModuleHandle(NULL);				// Grab An Instance For Our Window


	// Create Our OpenGL Window
	if (!CreateGLWindow("NeHe's Display List Tutorial 1", 0, 0, 640, 480, /*windsize, windsize,*/ 32, 0))
	{
		return 0;									// Quit If Window Was Not Created
	}
	//if (!CreateGLWindow("NeHe's Display List Tutorial 2", windsize, 0, windsize, windsize, 32, 1))
	//{
	//	return 0;									// Quit If Window Was Not Created
	//}
	//if (!CreateGLWindow("NeHe's Display List Tutorial 3", 0, windsize, windsize, windsize, 32, 2))
	//{
	//	return 0;									// Quit If Window Was Not Created
	//}
	//if (!CreateGLWindow("NeHe's Display List Tutorial 4", windsize, windsize, windsize, windsize, 32, 3))
	//{
	//	return 0;									// Quit If Window Was Not Created
	//}

	while(!done)									// Loop That Runs While done=FALSE
	{
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))	// Is There A Message Waiting?
		{
			if (msg.message==WM_QUIT)				// Have We Received A Quit Message?
			{
				done=TRUE;							// If So done=TRUE
			}
			else									// If Not, Deal With Window Messages
			{
				TranslateMessage(&msg);				// Translate The Message
				DispatchMessage(&msg);				// Dispatch The Message
			}
		}
		else										// If There Are No Messages
		{
			for (int id = 0; id < 4; id++)
			{
				if (_GLWindow[id].hWnd == 0)
					continue;

				// Draw The Scene.  Watch For ESC Key And Quit Messages From DrawGLScene()
				if (_GLWindow[id].keys[VK_ESCAPE])	// Active?  Was There A Quit Received?
				{
					done = TRUE;							// ESC or DrawGLScene Signalled A Quit
				}
				else									// Not Time To Quit, Update Screen
				{
					if (_GLWindow[id].keys[VK_LEFT])
					{
						_GLWindow[id].yrot -= 0.2f;
					}
					if (_GLWindow[id].keys[VK_RIGHT])
					{
						_GLWindow[id].yrot += 0.2f;
					}
					if (_GLWindow[id].keys[VK_UP])
					{
						_GLWindow[id].xrot -= 0.2f;
					}
					if (_GLWindow[id].keys[VK_DOWN])
					{
						_GLWindow[id].xrot += 0.2f;
					}
				}
			}
		}
	}

	// Shutdown
	KillGLWindow(0);									// Kill The Window
	//KillGLWindow(1);									// Kill The Window
	//KillGLWindow(2);									// Kill The Window
	//KillGLWindow(3);									// Kill The Window

	DWORD dLastError = GetLastError();

	if (!UnregisterClass("NeHe's Display List Tutorial 1", hInstance))			// Are We Able To Unregister Class
	{
		MessageBox(NULL, "Could Not Unregister Class.", "SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
		hInstance = NULL;									// Set hInstance To NULL
	}

	return (msg.wParam);							// Exit The Program
}